package cn.geckos.ioc.parsing
{
	import flash.utils.Dictionary;
	import flash.utils.getDefinitionByName;
	
	import cn.geckos.ioc.factory.Bean;
	import cn.geckos.ioc.parsing.Scope;
	import cn.geckos.util.LangUtil;
	import cn.geckos.ioc.factory.BeanFactoryAware;

	public class XMLConfigParser
	{
		private var _config:XML;
		private var _beans:Dictionary;
		private var errorMsg:String;
		
		public function XMLConfigParser(config:XML)
		{
			this._config = config;
			this._beans = new Dictionary();
		}
		
		public function set config(config:XML):void
		{
			this._config = config;
		}
		public function get config():XML
		{
			return this._config;
		}
		
		protected function getBean(id:String):*
		{
			var bean:Bean = _beans[id] as Bean;
			if(bean == null)
			{
				var errorMessage:String = "the bean refered didn't find";
				trace(errorMessage);
				throw new Error(errorMessage);
			}else
			{
				
				if(bean.scope == Scope.SINGLETON)
				{
					return bean.object;
				}else if(bean.scope == Scope.PROTOTYPE)
				{
					var no:Object = new (getDefinitionByName(bean.name) as Class);
					propertyType(_config.bean.(@id == id)[0], no);
					if(bean.init != null)
						no[bean.init]();
					return no;
				}
			}
		}
		
		protected function typeChoose(tp:XML):Object
		{
			switch(String(tp.name()))
			{
				case Type.REF:
					return referenceType(tp);
				break;
				case Type.MAP:
					return mapType(tp);
				break;
				case Type.STACK:
					return stackType(tp);
				break;
				default:
					var errorMsg:String = "property node defined error";
					trace(errorMsg);
					throw new Error(errorMsg);
				break;
			}
		}
		
		protected function processTopNode(topNode:XML):void
		{
			var children:XMLList = topNode.children();
			var o:Bean = null;
			
			for each(var node:XML in children)
			{
				switch(String(node.name()))
				{
					case Type.BEAN:
						o = beanType(node);
						if(o == null)
						{
							errorMsg = "parsing in bean node error occured";
							trace(errorMsg);
							throw new Error(errorMsg);
						}
						_beans[o.id] = o;
						o = null;
					break;
					default:
						var errorMsg:String = "Parse XML config syntax error";
						trace(errorMsg);
						throw new Error(errorMsg);
					break;
				}
			}
		}
		
		protected function injectProperty(topNode:XML):void
		{
			var children:XMLList = topNode.children();
			var property:XMLList = null;
			
			for each(var node:XML in children)
			{
				if(String(node.name()) == "bean")
				{
					var id:String = node.@id;
					var bean:Bean = _beans[id] as Bean;
					if(bean.scope == Scope.SINGLETON)
					{
						propertyType(node, bean.object);
						if(bean.init != null) bean.object[bean.init]();
					}
				}
			}
		}
		
		protected function beanType(node:XML):Bean
		{
			
			if(node.@id != undefined && node.@clazz != undefined)
			{
				var o:Bean = new Bean();
				o.id = node.@id;
				o.name = node.@clazz;

				var scope:String = node.@scope;
				if(node.@scope != undefined)
				{
					if(scope != Scope.PROTOTYPE && scope != Scope.SINGLETON)
					{
						var errorMsg:String = "bean's attitude defined error must be prototype or singleton";
						trace(errorMsg);
						throw new Error(errorMsg);
					}
					o.scope = scope;
				}else
				{
					o.scope = Scope.SINGLETON;
				}
				if(node.@init != undefined)
				{
					o.init = node.@init;
				}
				//trace(["output:=",o.id,o.name,o.scope,"="]);
				constructorInjection(node);
				var clazz:Class = getDefinitionByName(o.name) as Class;
				var bean:Object = new clazz();
				o.object = bean;
				
				return o;
			}
			return null;
		}
		
		protected function constructorInjection(node:XML):void
		{
			
		}
		
		protected function propertyType(node:XML, obj:Object):void
		{
			var property:XMLList = node.children();
			
			for each(var pro:XML in property)
			{
				if(String(pro.name()) == "property")
				{
					if(pro.@name != undefined)
					{
						var o:Object = null;
						if(pro.@value != undefined)
						{
							try
							{
								//trace(["output:=",pro.@name, pro.@value,"="]);

								obj[pro.@name]=String(pro.@value);
							}catch(e:Error)
							{
								obj[pro.@name](String(pro.@value));
							}
						}else if(pro.@ref != undefined)
						{
							o = this.getBean(String(pro.@ref));
							try
							{
								obj[pro.@name] = o;
							}catch(e:Error)
							{
								obj[pro.@name](o);
							}
						}else
						{
							var sub:XML = pro.children()[0];

							o = typeChoose(sub);
							
							try
							{
								obj[pro.@name] = o;
							}catch(e:Error)
							{
								obj[pro.@name](o);
							}
						}
					}
				}
			}
		}
		
		protected function referenceType(node:XML):Object
		{
			if(node.@bean != undefined)
			{
				//trace(node.@bean);
				return this.getBean(String(node.@bean));
			}else
			{
				var errorMsg:String = "ref node defined error";
				trace(errorMsg);
				throw new Error(errorMsg);
			}
			return null;
		}
		
		protected function mapType(node:XML):Object
		{
			var o:Dictionary = new Dictionary();
			
			for each(var entry:XML in node.children())
			{
				//trace(entry.name());
				if(String(entry.name()) == "entry")
				{
					var list:XMLList = entry.children();
					if(list.length() == 2)
					{
						var key:XML = list[0];
						var value:XML = list[1];
						if(String(key.name()) == "key")
						{
							if(String(value.name()) == "value")
							{
								o[String(key.@value)] = String(value);
							}else
							{
								o[String(key.@value)] = typeChoose(value);
							}
						}else
						{
							var errorMsg:String = "undefined key";
							trace(errorMsg);
							throw new Error(errorMsg);
						}
					}else
					{
						errorMsg = "key and value not matching";
						trace(errorMsg);
						throw new Error(errorMsg);
					}
				}else
				{
					errorMsg = "map defined error";
					trace(errorMsg);
					throw new Error(errorMsg);
				}
			}
			return o;
		}
		
		protected function stackType(node:XML):Object
		{
			var o:Array = new Array();
			
			for each(var v:XML in node.children())
			{
				if(String(v.name()) == "value")
				{
					o.push(String(v));
				}else
				{
					o.push(typeChoose(v));
				}
			}
			return o;
		}
		
	}
}